#include "font.h"

using namespace bt;

Font::Font()
{
	textures = NULL;
	
	fontSize = 0;
	lineHeight = 0;
}

Font::Font(const char* filename, int fontSize)
{
	textures = NULL;
	
	setFont(filename, fontSize);
}

Font::~Font()
{
	// Delete old Textures and Display Lists if necessary
	if(textures)
	{
		glDeleteLists(lists, 128);
		glDeleteTextures(128, textures);
		delete [] textures;
	}
}

// Accessors
float Font::getFontSize() { return fontSize; }

float Font::getLineHeight() { return lineHeight; }

// Mutators
void Font::setFont(const char* filename, int fontSize)
{
	Uint8* buffer;
	PHYSFS_sint64 filesize = FS_readFile(buffer, filename);
	
	if(buffer)
	{
		// Initialize FreeType library
		FT_Library library;

		if(FT_Init_FreeType(&library))
		{
			fprintf(stderr, "Font: Unable to initialize FreeType library.\n", filename);
			return;
		}
		
		// Load font face
		FT_Face face;
		
		if(FT_New_Memory_Face(library, (const FT_Byte*)buffer, filesize, 0, &face))
		{
			fprintf(stderr, "Font: Unable to create font face from Memory.\n", filename);
			return;
		}

		// Set font size to 1/64th pt * 2^6 * fontSize
		// at 96x96 dpi
		this->fontSize = (float)fontSize;
		lineHeight = fontSize * 1.25f;
		FT_Set_Char_Size(face, 0, fontSize << 6, 96, 96);
		
		// Delete old Textures and Display Lists if necessary
		if(textures)
		{
			glDeleteLists(lists, 128);
			glDeleteTextures(128, textures);
			delete [] textures;
		}
		
		// Initialize Textures and Display Lists
		lists = glGenLists(128);
		textures = new GLuint[128];
		glGenTextures(128, textures);
		
		// Create a display list for each character
		for(unsigned char i = 0; i < 128; i++)
		{
			genList(face, i);
		}
		
		// Clean up font face
		FT_Done_Face(face);
		
		// Clean up FreeType library
		FT_Done_FreeType(library);
	}
	else
	{
		fprintf(stderr, "Font: Unable to load font from file: %s\n", filename);
		return;
	}
	
	// Clean up loaded file
	delete [] buffer;
}

void Font::setLineHeight(float height) { lineHeight = height; }

// Actions
void Font::drawString(const char* output)
{
	drawString(output, 0.0f, 0.0f);
}

void Font::drawString(const char* output, float x, float y)
{
	std::string input = output;
	std::string curLine;
	
	glListBase(lists);
	glRasterPos2i(0, 0);
	
	for(int line = 1; !input.empty(); line++)
	{
		curLine = cutFirstToken(input, "\n");
		
		glPushMatrix();
		
			// Render the current line
			glTranslatef(x, y + (float)line*lineHeight, 0.0f);
			
			glCallLists(curLine.length(), GL_UNSIGNED_BYTE, curLine.c_str());
		
		glPopMatrix();
	}
}

void Font::drawClippedString(const char* output, float w, float h)
{
	drawClippedString(output, 0, 0, w, h);
}

void Font::drawClippedString(const char* output, float x, float y, float w, float h)
{
	std::string input = output;
	std::string curLine;
	
	GLfloat rasterPos[4];
	GLfloat viewport[4];
	
	// Reset raster pos to start from top left
	// It normally starts from bottom left
	glGetFloatv(GL_VIEWPORT, viewport);
	glRasterPos4f(0, viewport[3], 0, 1);
	
	// Loop through lines of input string
	// print them individually
	for(int line = 1; !input.empty(); line++)
	{
		curLine = cutFirstToken(input, "\n");
		int lineLength = curLine.length();
		
		glPushMatrix();
		
			// Render the current line
			glTranslatef(x, y + (float)line*lineHeight, 0.0f);
			
			for(int i = 0; i < lineLength; i++)
			{
				// Render a character
				glCallList(lists + curLine.at(i));
				
				// Stop rendering this line if
				// the next character would be out of bounds
				glGetFloatv(GL_CURRENT_RASTER_POSITION, rasterPos);
				
				if(rasterPos[0]+fontSize > w)
					break;
			}
		
		glPopMatrix();
		
		// Done if no more lines fit in bounds
		if(rasterPos[1]+fontSize > h)
			break;
		
		// Reset raster pos to left for new line
		glBitmap(0, 0, 0, 0, -rasterPos[0], 0, NULL);
	}
}

void Font::drawWrappedString(const char* output, float w, float h)
{
	drawWrappedString(output, 0, 0, w, h);
}

void Font::drawWrappedString(const char* output, float x, float y, float w, float h)
{
	std::string input = output;
	std::string curLine;
	
	GLfloat rasterPos[4];
	GLfloat viewport[4];
	
	// Reset raster pos to start from top left
	// It normally starts from bottom left
	glGetFloatv(GL_VIEWPORT, viewport);
	glRasterPos4f(0, viewport[3], 0, 1);
	
	// Loop through lines of input string
	// print them individually
	for(int line = 1; !input.empty(); line++)
	{
		curLine = cutFirstToken(input, "\n");
		int lineLength = curLine.length();
		
		glPushMatrix();
		
			// Render the current line
			glTranslatef(x, y + (float)line*lineHeight, 0.0f);
			
			for(int i = 0; i < lineLength; i++)
			{
				// Render a character
				glCallList(lists + curLine.at(i));
				
				// Go to next line if next character
				// would be out of the specified bounds
				glGetFloatv(GL_CURRENT_RASTER_POSITION, rasterPos);
				
				if(rasterPos[0]+fontSize > w)
				{
					glTranslatef(-rasterPos[0], lineHeight, 0.0f);
					glBitmap(0, 0, 0, 0, -rasterPos[0], lineHeight, NULL);

					// Done if no more lines fit in bounds
					if(rasterPos[1]+fontSize > h)
					{
						glPopMatrix();
						return;
					}
					
					line++;
				}
			}
		
		glPopMatrix();
		
		// Done if no more lines fit in bounds
		if(rasterPos[1]+fontSize > h)
			break;
		
		// Reset raster pos to left for new line
		glBitmap(0, 0, 0, 0, -rasterPos[0], 0, NULL);
	}
}

// Private
void Font::genList(FT_Face& face, char ch)
{
	// Get the glyph for the character
	int glyphIndex = FT_Get_Char_Index(face, ch);
	if(glyphIndex == 0 )
		return;
	
	int error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_NO_BITMAP);
	if(error)
	{
		fprintf(stderr, "Font: Unable to load glyph from font.\n");
		return;
	}

	FT_Glyph glyph;
	if(FT_Get_Glyph(face->glyph, &glyph))
	{
		fprintf(stderr, "Font: Unable to create glyph object.\n");
		return;
	}

	// Convert the glyph to a
	// High quality, anti-aliased, 256 levels of gray bitmap
	FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
	FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph)glyph;

	// Make a reference to the bitmap
	FT_Bitmap& bitmap = bitmapGlyph->bitmap;
	
	// Prepare GL Bitmap data for loading
	int width = nextPowerOf2(bitmap.width);
	int height = nextPowerOf2(bitmap.rows);
	
	GLubyte* expandedData = new GLubyte[2 * width * height];

	// Borrowed from NeHe tutorial to make 2 channel bitmap (luminance + alpha)
	for(int j = 0; j < height; j++)
	{
		for(int i = 0; i < width; i++)
		{
			expandedData[2*(i+j*width)] = expandedData[2*(i+j*width)+1] = 
				(i>=bitmap.width || j>=bitmap.rows) ?
				0 : bitmap.buffer[i + bitmap.width*j];
		}
	}
	
	// Set texture properties
	glBindTexture(GL_TEXTURE_2D, textures[ch]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
				 GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expandedData);
	
	delete [] expandedData;
	
	// Create the Display List
	float drawX = (float)bitmap.width / (float)width;
	float drawY = (float)bitmap.rows / (float)height;
	
	glNewList(lists + ch, GL_COMPILE);
	
		glBindTexture(GL_TEXTURE_2D, textures[ch]);
	
		glPushMatrix();
		
			glTranslatef(bitmapGlyph->left, 0.0f, 0.0f);
			glTranslatef(0.0f, -bitmapGlyph->top, 0.0f);
	
			glBegin(GL_QUADS);
				glTexCoord2d(0,0); glVertex2f(0,0);
				glTexCoord2d(0,drawY); glVertex2f(0,bitmap.rows);
				glTexCoord2d(drawX,drawY); glVertex2f(bitmap.width,bitmap.rows);
				glTexCoord2d(drawX,0); glVertex2f(bitmap.width,0);
			glEnd();
			
		glPopMatrix();
	
		// Prepare for next character
		glTranslatef(face->glyph->advance.x >> 6, 0, 0);
		glBitmap(0, 0, 0, 0, face->glyph->advance.x >> 6, 0, NULL);
	
	glEndList();
}
